/*
 * Copyright (C) 2014 Sony Computer Entertainment Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"
#include "TiledBufferPainter.h"

#include "Frame.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "InspectorController.h"
#include "Page.h"
#include "TransformationMatrix.h"
#include <wtf/CurrentTime.h>
#include <wtf/StringExtras.h>

static bool gShowPaintTime = false;
static bool gShowDirtyRects = false;

static void showPaintTime(cairo_t *cr, double paintTime);

namespace {

class FallbackPainterCairo : public tilebackend::PainterInterface {
public:
    FallbackPainterCairo();

    virtual int beginPaint(tilebackend::PlatformSurface*);
    virtual int endPaint();

    virtual int drawTexture(const tilebackend::Texture&, const tilebackend::FloatRect&, const tilebackend::TransformationMatrix&, float);
    virtual int drawSolidColor(const tilebackend::FloatRect&, const tilebackend::TransformationMatrix&, const tilebackend::Color&);
#if USE_TILEBACKEND_V3_API
    virtual int drawBorder(const tilebackend::FloatRect&, const tilebackend::TransformationMatrix&, const tilebackend::Color&);
    virtual int scissor(const tilebackend::FloatRect&, bool enable) { return 0; }
#endif
private:
    void doPaint(const tilebackend::Texture& , const tilebackend::FloatRect&);
    void doPaintFast(const tilebackend::Texture& , const tilebackend::FloatRect&);

    cairo_t* m_cr;
};

FallbackPainterCairo::FallbackPainterCairo()
    : m_cr(0)
{
}

int FallbackPainterCairo::beginPaint(tilebackend::PlatformSurface* target)
{
    if (!target)
        CRASH();

    // create target surface and cairo context
    cairo_surface_t* surface =
        cairo_image_surface_create_for_data(
            (unsigned char*)target->buffer,
            CAIRO_FORMAT_ARGB32,
            target->width,
            target->height,
            target->stride);
    
    cairo_t* cr = cairo_create(surface);
    if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
        CRASH();
    cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
    m_cr = cr;
    return 0;
}

int FallbackPainterCairo::endPaint()
{
    if (cairo_status(m_cr) != CAIRO_STATUS_SUCCESS)
        CRASH();

    cairo_surface_t* surface = cairo_get_target(m_cr);
    cairo_destroy(m_cr);
    cairo_surface_destroy(surface);

    m_cr = 0;
    return 0;
}

int FallbackPainterCairo::drawTexture(const tilebackend::Texture& texture, const tilebackend::FloatRect& rect, const tilebackend::TransformationMatrix& matrix, float)
{
    if (rect.w <= 0 || rect.h <= 0)
        return 0;

    cairo_matrix_t cairoMatrix;
    cairo_matrix_init(&cairoMatrix, matrix.m[0][0], matrix.m[1][0], matrix.m[1][0], matrix.m[1][1], matrix.m[0][3], matrix.m[1][3]);

    cairo_save(m_cr);
    cairo_set_matrix(m_cr, &cairoMatrix);

    if (matrix.m[0][0] == 1.0f && matrix.m[1][1] == 1.0f && !matrix.m[1][0] && !matrix.m[0][1])
        doPaintFast(texture, rect);
    else
        doPaint(texture, rect);

    cairo_restore(m_cr);
    return 0;    
}

int FallbackPainterCairo::drawSolidColor(const tilebackend::FloatRect& rect, const tilebackend::TransformationMatrix& matrix, const tilebackend::Color& color)
{
    // fill
    cairo_set_source_rgb(m_cr, color.rgba[0], color.rgba[1], color.rgba[2]);
    cairo_pattern_set_extend(cairo_get_source(m_cr), CAIRO_EXTEND_PAD);
    cairo_set_operator(m_cr, CAIRO_OPERATOR_SOURCE);
    cairo_rectangle(m_cr, rect.x, rect.y, rect.w, rect.h);
    cairo_fill(m_cr);

    // draw tile frame
    cairo_set_source_rgb(m_cr, 1.0, 0.0, 1.0);
    cairo_rectangle(m_cr, rect.x, rect.y, rect.w, rect.h);
    cairo_stroke(m_cr);
    return 0;
}

#if USE_TILEBACKEND_V3_API
int FallbackPainterCairo::drawBorder(const tilebackend::FloatRect& rect, const tilebackend::TransformationMatrix& matrix, const tilebackend::Color& color)
{
    // draw tile frame
    cairo_set_source_rgb(m_cr, color.rgba[0], color.rgba[1], color.rgba[2]);
    cairo_rectangle(m_cr, rect.x, rect.y, rect.w, rect.h);
    cairo_stroke(m_cr);
    return 0;
}
#endif

void FallbackPainterCairo::doPaint(const tilebackend::Texture& texture, const tilebackend::FloatRect& rect)
{
    // create a image surface of tile buffer
    cairo_surface_t* surface =
        cairo_image_surface_create_for_data(
        (unsigned char*)texture.buffer,
        CAIRO_FORMAT_ARGB32,
        texture.width,
        texture.height,
        texture.stride);

    if (surface) {
        cairo_set_source_surface(m_cr, surface, rect.x, rect.y);
        cairo_pattern_set_extend(cairo_get_source(m_cr), CAIRO_EXTEND_PAD);
        cairo_pattern_set_filter(cairo_get_source(m_cr), CAIRO_FILTER_FAST);
        cairo_set_operator(m_cr, CAIRO_OPERATOR_OVER);

        cairo_rectangle(m_cr, rect.x, rect.y, rect.w, rect.h);
        cairo_fill(m_cr);

        cairo_surface_finish(surface);
        cairo_surface_destroy(surface);
    }

    // draw tile frame
    cairo_set_source_rgb(m_cr, 1.0, 0.0, 0.0);
    cairo_rectangle(m_cr, rect.x, rect.y, rect.w, rect.h);
    cairo_stroke(m_cr);
}

void FallbackPainterCairo::doPaintFast(const tilebackend::Texture& texture, const tilebackend::FloatRect& rect)
{
    if (rect.x != (int)rect.x || rect.y != (int)rect.y || rect.w != (int)rect.w || rect.h != (int)rect.h) {
        doPaint(texture, rect);
        return;
    }

    cairo_matrix_t matrix;
    cairo_get_matrix(m_cr, &matrix);
    
    cairo_surface_t* target = cairo_get_target(m_cr);
    int dstStride = cairo_image_surface_get_stride(target);
    int dstWidth  = cairo_image_surface_get_width(target);
    int dstHeight = cairo_image_surface_get_height(target);
    WebCore::IntRect dstRect(-matrix.x0, -matrix.y0, dstWidth, dstHeight);

    WebCore::IntRect paintRect = dstRect;
    paintRect.intersect(WebCore::IntRect((int)rect.x, (int)rect.y, (int)rect.w, (int)rect.h));
    if (paintRect.isEmpty())
        return;

    // src
    int srcX = paintRect.x() - (int)rect.x;
    int srcY = paintRect.y() - (int)rect.y;
    uint32_t* src = (uint32_t*)texture.buffer;
    src += srcX + srcY * texture.width;

    // dst
    int dstX = paintRect.x() - dstRect.x();
    int dstY = paintRect.y() - dstRect.y();
    uint8_t* dst = (uint8_t*)cairo_image_surface_get_data(target);
    dst += dstX * sizeof(uint32_t) + dstY * dstStride;

    // do copy
    for (int y = 0; y < paintRect.height(); ++y) {
        memcpy(dst, src, paintRect.width()*sizeof(uint32_t));
        src += texture.width;
        dst += dstStride;
    }
}

FallbackPainterCairo s_fallbackPainter;

}

namespace {

class FallbackPool : public tilebackend::PoolInterface {
public:
    FallbackPool()
        : m_tileSize() { }

    virtual tilebackend::TileSize getTileSize() const
    {
        return m_tileSize;
    }

    virtual tilebackend::Texture allocateTileTexture()
    {
        tilebackend::Texture texture;
        texture.width = m_tileSize.width;
        texture.height = m_tileSize.height;
        texture.format = tilebackend::kARGB32;
        texture.stride = texture.width * tilebackend::BytesPerPixel<tilebackend::kARGB32>::value;
        texture.buffer = malloc(texture.memSize());
        return texture;
    }

    virtual void freeTileTexture(tilebackend::Texture &texture)
    {
        free(texture.buffer);
        texture.buffer = 0;
    }

#if USE_TILEBACKEND_V3_API
    virtual void* allocateMemory(int size)
    {
        return malloc(size);
    }

    virtual void freeMemory(void *addr)
    {
        free(addr);
    }

    virtual tilebackend::Texture allocateTexture(tilebackend::IntSize textureSize, tilebackend::PixelFormat)
    {
        tilebackend::Texture texture;
        texture.width = textureSize.width;
        texture.height = textureSize.height;
        texture.stride = textureSize.width * 4;
        texture.format = tilebackend::kARGB32;
        texture.buffer = malloc(texture.stride * texture.height);
        return texture;
    }

    virtual void freeTexture(tilebackend::Texture &texture)
    {
        free(texture.buffer);
        texture.buffer = 0;
    }

#if DEBUG_TILEDBACKEND_V2
    virtual void reportMemoryChunkInfo(MemoryChunkInfoCallback, void* usrdata) { }
#endif

#endif


private:
    tilebackend::TileSize m_tileSize;
};

FallbackPool s_fallbackPool;

}

namespace {

class WorkerThread {
    WTF_MAKE_NONCOPYABLE(WorkerThread);
public:
    typedef void (*ThreadFunction)(void*);
    
    WorkerThread(const char* threadName)
        : m_threadId(0)
#if USE(MANX_COND_INIT)
        , m_threadCondition(m_mutex)
#endif
        , m_threadFunction(0)
        , m_parameters(0)
        , m_running(false)
    {
        m_threadId = createThread(workerThread, this, threadName);
        if (!m_threadId)
            CRASH();
    }

    void execute(ThreadFunction threadFunction, void* parameters)
    {
        MutexLocker lock(m_mutex);

        m_threadFunction = threadFunction;
        m_parameters = parameters;
        m_running = true;
        m_threadCondition.signal();
    }

    void waitForFinish()
    {
        MutexLocker lock(m_mutex);

        while (m_running)
            m_threadCondition.wait(m_mutex);
    }

    void workerThread()
    {
        MutexLocker lock(m_mutex);

        while (true) {
            if (m_running) {
                (*m_threadFunction)(m_parameters);
                m_running = false;
                m_threadFunction = 0;
                m_parameters = 0;
                m_threadCondition.signal();
            }

            m_threadCondition.wait(m_mutex);
        }
    }

    static void workerThread(void* that)
    {
        ((WorkerThread*)that)->workerThread();
    }

private:
    mutable Mutex m_mutex;
    ThreadIdentifier m_threadId;
    ThreadCondition m_threadCondition;

    ThreadFunction m_threadFunction;
    void* m_parameters;
    bool m_running;
};

WorkerThread& workerThread()
{
    static WorkerThread s_workerThread("SceManxTileUpdateThread");
    return s_workerThread;
}

}

namespace {

cairo_surface_t* createCairoSurface(const WebCore::IntSize& size)
{
    unsigned width = size.width();
    unsigned height = size.height();
    if (width >= 0x7FFF || height >= 0x7FFF)
        CRASH();

    unsigned stride = width * 4; // ARGB32
    unsigned memSize = stride * height;
    void* buffer = memalign(32, memSize);
    if (!buffer)
        return 0;

    cairo_surface_t* surface = cairo_image_surface_create_for_data(
        (unsigned char*)buffer,
        CAIRO_FORMAT_ARGB32,
        width,
        height,
        stride);
    if (!surface)
        CRASH();

    ASSERT(cairo_image_surface_get_data(surface) == buffer);
    return surface;
}

void toRGB24(uint8_t* dst, const uint8_t* src, int count)
{
#if OS(PSP2)
    while (count >= 8) {
        uint8x8x4_t srcVec = vld4_u8((const uint8_t*)src);
        uint8x8x3_t dstVec;
        dstVec.val[0] = srcVec.val[0];
        dstVec.val[1] = srcVec.val[1];
        dstVec.val[2] = srcVec.val[2];
        vst3_u8(dst, dstVec);
        dst += 3 * 8;
        src += 4 * 8;
        count -= 8;
    }
#endif

    while (count-- > 0) {
        *dst++ = *src++;
        *dst++ = *src++;
        *dst++ = *src++;
        src++;
    }
}

}

namespace WebCore {

TiledBufferPainter::TiledBufferPainter()
{
}

TiledBufferPainter::~TiledBufferPainter()
{
}

TiledBufferPainter* TiledBufferPainter::create(const IntSize& screenSize, tilebackend::PoolInterface* pool, tilebackend::PainterInterface* painter)
{
    // create instance
    TiledBufferPainter* that = new TiledBufferPainter;
    if (that) {
        if (!pool)
            pool = &s_fallbackPool;

        tilebackend::TileSize tileSize = pool->getTileSize();

        that->m_backgroundColor = Color::white;
        that->m_contentsScale = 0.0f;
        that->m_committedScale = 0.0f;
        that->m_screenSize = screenSize;
        that->m_pool = pool;
        that->m_painter = painter;
        that->m_tileSize = tileSize;
    }

    return that;
}

void TiledBufferPainter::setContentsScale(float contentsScale)
{
    m_contentsScale = contentsScale;
}

bool TiledBufferPainter::updateTiles(FrameView* view)
{
    tilebackend::PoolInterface* pool = m_pool;
    if (!pool)
        CRASH();
    if (!m_contentsScale)
        return true; // Needs repaint in this case.

    bool needsRepaint = false;

    view->setPaintsEntireContents(true);

    IntRect viewRect = view->visibleContentRect();

    IntSize tileSize = this->tileSize();
    FloatRect rect = (FloatRect)viewRect;
    if (m_contentsScale > 0) {
        FloatSize margin = tileSize;
        margin.scale(1 / m_contentsScale);
        rect.expand(margin);
        margin.scale(-0.5);
        rect.move(margin);
    }
    rect.scale(m_contentsScale / tileSize.width(), m_contentsScale / tileSize.height());

    // rect in tile coord to cover VisibleContent
    IntRect coverRect = enclosingIntRect(rect);

    // expand cover rect
    FloatRect contentsRect(FloatPoint(), view->contentsSize());
    contentsRect.scale(m_contentsScale / tileSize.width(), m_contentsScale / tileSize.height());
    coverRect.intersect(enclosingIntRect(contentsRect));

    TileMap tiles; // A TileMap which update tiles are stored.

    if (m_committedScale == m_contentsScale) {
        // get dirty tiles
        Vector<TiledBuffer::Coordinate> dirtyTileCoords;
        updateDirtyRects();
        getDirtyTileCoords(coverRect, dirtyTileCoords);

        // for each dirty tile coords...
        unsigned size = dirtyTileCoords.size();
        for (unsigned i = 0; i < size; ++i) {
            TiledBuffer::Coordinate coord = dirtyTileCoords[i];

            // crate tile
            RefPtr<TiledBuffer> tile = TiledBuffer::create(coord, pool);

            // in case tile pool is empty, release tile and retry.
            if (!tile && releaseMostDistantTile(viewRect))
                tile = TiledBuffer::create(coord, pool);

            if (tile) {
                IntRect targetRect = tile->tileRect();
                TiledBuffer* sourceTilePtr = 0;
                RefPtr<TiledBuffer> sourceTile = m_tiles.get(coord);
                if (sourceTile && sourceTile->isReadyForPartialRepaint(m_contentsScale)) {
                    // Copy already painted tile for partial painting.
                    targetRect = sourceTile->targetRectForPartialRepaint();
                    sourceTilePtr = sourceTile.get();
                }
                cairo_surface_t* surface = createCairoSurface(targetRect.size());
                paintContents(view, surface, targetRect);
                updateTileContents(tile.get(), surface, targetRect, sourceTilePtr);
                cairo_surface_destroy(surface);                    
                
                tile->setContentsScale(m_contentsScale);
                tile->setValid(true);
                tile->clearDirtyRect();
                tiles.set(coord, tile);
            } else
                needsRepaint = true;
        }
    } else {
        // ContentsScale changed. Update all tiles within coverRect.
        int left   = coverRect.x();
        int right  = coverRect.maxX();
        int top    = coverRect.y();
        int bottom = coverRect.maxY();

        int tileWidth = m_tileSize.width;
        int tileHeight = m_tileSize.height;

        for (int y = top; y < bottom; ++y) {
            for (int x = left; x < right; ++x) {
                TiledBuffer::Coordinate coord(x, y);

                // crate tile
                RefPtr<TiledBuffer> tile = TiledBuffer::create(coord, pool);

                // in case tile pool is empty, release tile and retry.
                if (!tile && releaseMostDistantTile(viewRect))
                    tile = TiledBuffer::create(coord, pool);

                if (tile) {
                    cairo_surface_t* surface = createCairoSurface(IntSize(tileWidth, tileHeight));
                    paintContents(view, surface, tile->tileRect());
                    updateTileContents(tile.get(), surface, tile->tileRect());
                    cairo_surface_destroy(surface);
                    tile->setContentsScale(m_contentsScale);
                    tile->setValid(true);
                    tile->clearDirtyRect();
                    tiles.set(coord, tile);
                } else
                    needsRepaint = true;
            }
        }
    }

    workerThread().waitForFinish();

    // commit
    commitTiles(tiles, m_contentsScale);

    Color backgroundColor = view->documentBackgroundColor();
    if (backgroundColor.isValid())
        m_backgroundColor.setRGB(backgroundColor.rgb() | 0xFF000000U);

    m_dirtyRects.clear();

    return needsRepaint;
}

void TiledBufferPainter::updateTileContents(TiledBuffer* tile, cairo_surface_t* surface, const IntRect& targetRect, TiledBuffer* original)
{
    class Updater {
    public:
        struct Parameters {
            int surfaceWidth;
            int surfaceHeight;
            int surfaceStride;
            uint8_t* surfaceData;

            int tileWidth;
            int tileHeight;
            int tileStride;
            tilebackend::PixelFormat tileFormat;

            IntRect targetRect;

            uint8_t* destinationBuffer;
            uint8_t* originalBuffer;
        };

        static void update(void* usrdata)
        {
            const Parameters* param = (const Parameters*)usrdata;

            const IntRect& targetRect = param->targetRect;
            if (param->originalBuffer) {
                uint8_t* end = param->destinationBuffer + param->tileStride * param->tileHeight;

                const int ThresholdWidth = 32; // A heuristic threshold.
                if (targetRect.width() <= ThresholdWidth)
                    memcpy(param->destinationBuffer, param->originalBuffer, param->tileStride * param->tileHeight);
                else {
                    //          X               X + W
                    //          |               |
                    // +------------------------------+
                    // |             Top              |
                    // +--------+---------------+-----+ - Y
                    // |  Top   |               |     |
                    // +--------|               |  M  |
                    // |        |               |  i  |
                    // |   M    |               |  d  |
                    // |   i    |               |  d  |
                    // |   d    |               |  l  |
                    // |   d    |               |  e  |
                    // |   l    |               |-----|
                    // |   e    |               | Btm |
                    // +--------+---------------+-----+ - Y + H
                    // |            Bottom            |
                    // +------------------------------+

                    const int bpp = param->tileFormat == tilebackend::kARGB32 ? 4 : 3;
                    uint8_t* src;
                    uint8_t* dst;
                    uint8_t* last;
                    int offset;
                    int stride = param->tileStride;

                    // Copy top part.
                    dst = param->destinationBuffer;
                    last = dst + stride * targetRect.y();
                    last += targetRect.x() * bpp;
                    if (dst < last && last <= end) {
                        src = param->originalBuffer;
                        memcpy(dst, src, last - dst);
                    }
                    
                    // Copy middle part.
                    int copySize = stride - targetRect.width() * bpp;
                    if (copySize > 0 && targetRect.height() > 1) {
                        offset = stride * targetRect.y() + (targetRect.x() + targetRect.width()) * bpp;
                        src = param->originalBuffer + offset;
                        dst = param->destinationBuffer + offset;
                        int count = targetRect.height() - 1;
                        while (count-- > 0) {
                            if (dst + copySize <= end)
                                memcpy(dst, src, copySize);
                            dst += stride;
                            src += stride;
                        }
                    }

                    // Copy bottom part.
                    offset = stride * (targetRect.y() + targetRect.height() - 1) + (targetRect.x() + targetRect.width()) * bpp;
                    dst = param->destinationBuffer + offset;
                    src = param->originalBuffer + offset;
                    if (dst < end)
                        memcpy(dst, src, end - dst);
                }
            }

            if (param->tileFormat == tilebackend::kARGB32) {
                const int bpp = tilebackend::BytesPerPixel<tilebackend::kARGB32>::value;
                if (param->surfaceWidth == param->tileWidth && param->surfaceStride == param->tileStride) {
                    ASSERT(!targetRect.x());
                    uint8_t* src = param->surfaceData;
                    uint8_t* dst = param->destinationBuffer + param->tileStride * targetRect.y();
                    size_t copySize = param->tileStride * targetRect.height();
                    uint8_t* end = param->destinationBuffer + param->tileStride * param->tileHeight;
                    if (dst + copySize <= end)
                        memcpy(dst, src, copySize);
                } else {
                    uint8_t* src = param->surfaceData;
                    uint8_t* dst = param->destinationBuffer + param->tileStride * targetRect.y() + targetRect.x() * bpp;
                    size_t copySize = targetRect.width() * bpp;
                    uint8_t* end = param->destinationBuffer + param->tileStride * param->tileHeight;
                    for (int y = 0; y < targetRect.height(); y++) {
                        if (dst + copySize <= end)
                            memcpy(dst, src, copySize);
                        src += param->surfaceStride;
                        dst += param->tileStride;
                    }
                }
            } else if (param->tileFormat == tilebackend::kRGB24) {
                const int bpp = tilebackend::BytesPerPixel<tilebackend::kRGB24>::value;
                if (param->surfaceWidth == param->tileWidth && param->surfaceStride == param->tileStride) {
                    ASSERT(!targetRect.x());
                    uint8_t* src = param->surfaceData;
                    uint8_t* dst = param->destinationBuffer + param->tileStride * targetRect.y();
                    size_t copySize = param->tileStride * targetRect.height();
                    uint8_t* end = param->destinationBuffer + param->tileStride * param->tileHeight;
                    if (dst + copySize <= end)
                        toRGB24(dst, src, copySize / bpp);
                } else {
                    uint8_t* src = param->surfaceData;
                    uint8_t* dst = param->destinationBuffer + param->tileStride * targetRect.y() + targetRect.x() * bpp;
                    size_t copySize = targetRect.width() * bpp;
                    uint8_t* end = param->destinationBuffer + param->tileStride * param->tileHeight;
                    for (int y = 0; y < targetRect.height(); y++) {
                        if (dst + copySize <= end)
                            toRGB24(dst, src, copySize / bpp);
                        src += param->surfaceStride;
                        dst += param->tileStride;
                    }
                }
            } else
                CRASH();

            free(param->surfaceData);
            free(usrdata);
        }
    };

    Updater::Parameters* param = new Updater::Parameters;
    if (!param)
        CRASH();

    // Set up parameters.
    param->surfaceWidth = cairo_image_surface_get_width(surface);
    param->surfaceHeight = cairo_image_surface_get_height(surface);
    param->surfaceStride = cairo_image_surface_get_stride(surface);
    param->surfaceData = cairo_image_surface_get_data(surface);
    ASSERT(targetRect.width() == param->surfaceWidth);
    ASSERT(targetRect.height() == param->surfaceHeight);
    ASSERT(param->surfaceData);

    const tilebackend::Texture& texture = tile->texture();

    param->tileWidth = texture.width;
    param->tileHeight = texture.height;
    param->tileStride = texture.stride;
    param->tileFormat = texture.format;
    param->destinationBuffer = (uint8_t*)texture.buffer;

    if (original)
        param->originalBuffer = (uint8_t*)original->texture().buffer;
    else
        param->originalBuffer = 0;

    param->targetRect = targetRect;
    param->targetRect.moveBy(IntPoint(-tile->coordinate().x() * texture.width, -tile->coordinate().y() * texture.height));

    workerThread().execute(Updater::update, param);
}

void TiledBufferPainter::paintContents(FrameView* view, cairo_surface_t* surface, const IntRect& targetRect)
{
    // Rect in the document coord to paint.
    IntRect paintRect;
    FloatRect floatRect = targetRect;
    floatRect.scale(1 / m_contentsScale);
    paintRect = enclosingIntRect(floatRect);

    // Create cairo context.
    cairo_t* cr = cairo_create(surface);
    if (cairo_status(cr) != CAIRO_STATUS_SUCCESS)
        CRASH();

    // Paint contents to surface
    double paintTime;
    if (gShowPaintTime)
        paintTime = WTF::currentTime();

    WebCore::GraphicsContext ctx(cr);
    ctx.save();
    ctx.translate(-targetRect.x(), -targetRect.y());
    ctx.scale(FloatSize(m_contentsScale, m_contentsScale));
    view->paintContents(&ctx, paintRect);
#if ENABLE(INSPECTOR)
    if (view->frame()->page()->inspectorController()->highlightedNode())
        view->frame()->page()->inspectorController()->drawHighlight(ctx);
#endif
    ctx.restore();

    if (gShowPaintTime) {
        paintTime = WTF::currentTime() - paintTime;
        // Show paint time.
        showPaintTime(cr, paintTime * 1000.0);
    }

    if (gShowDirtyRects) {
        ctx.save();
        cairo_set_source_rgba(cr, 1, 0, 1, 0.5);

        cairo_translate(cr, -(double)targetRect.x(), -(double)targetRect.y());
        cairo_scale(cr, m_contentsScale, m_contentsScale);

        // Check dirtyRects.
        unsigned size = m_dirtyRects.size();
        for (unsigned i = 0; i < size; ++i) {
            WebCore::FloatRect rect = m_dirtyRects[i];

            cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
            cairo_stroke(cr);
        }
        ctx.restore();
    }

    // Destroy cairo context.
    cairo_destroy(cr);
}

void TiledBufferPainter::commitTiles(TileMap &tiles, float contentsScale)
{
    WTF::MutexLocker locker(m_mutex);

    // release tiles which contentsScales are different from current contentsScale
    for (TileMap::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it) {
        if (it->value && it->value->contentsScale() != contentsScale)
            it->value.release();
    }

    // set new tiles
    for (TileMap::iterator it = tiles.begin(); it != tiles.end(); ++it) {
        if (it->value)
            m_tiles.set(it->key, it->value);
    }

    // removed empty tiles
    bool done;
    do {
        done = true;
        for (TileMap::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it) {
            if (!it->value) {
                m_tiles.remove(it);
                done = false;
                break;
            }
        }
    } while (!done);

    m_committedScale = contentsScale;
}

void TiledBufferPainter::updateDirtyRects()
{
    for (TileMap::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it) {
        RefPtr<TiledBuffer> tile = it->value;
        if (!tile || tile->contentsScale() != m_contentsScale)
            continue;

        // check dirtyRects
        IntRect rect = tile->enclosingScaledRect();
        unsigned size = m_dirtyRects.size();
        for (unsigned i = 0; i < size; ++i) {
            IntRect dirtyRect = m_dirtyRects[i];
            dirtyRect.intersect(rect);
            if (!dirtyRect.isEmpty())
                tile->uniteDirtyRect(dirtyRect);
        }
    }
}

void TiledBufferPainter::getDirtyTileCoords(const IntRect& coverRect, Vector<TiledBuffer::Coordinate>& dirtyTileCoords)
{
    int left   = coverRect.x();
    int right  = coverRect.maxX();
    int top    = coverRect.y();
    int bottom = coverRect.maxY();

    for (int y = top; y < bottom; ++y) {
        for (int x = left; x < right; ++x) {
            TiledBuffer::Coordinate coord(x, y);
            IntRect tileRect = TiledBuffer::enclosingScaledRect(tileSize(), coord, m_contentsScale);

            RefPtr<TiledBuffer> tile = m_tiles.get(coord);
            if (!tile || tile->contentsScale() != m_contentsScale || tile->isDirty()) {
                dirtyTileCoords.append(coord);
                continue;
            }
        }
    }
}

bool TiledBufferPainter::releaseMostDistantTile(const IntRect& viewRect)
{
    WTF::MutexLocker locker(m_mutex);

    IntPoint center = viewRect.center();

    // find most distant tile
    TileMap::iterator candidate = m_tiles.end();
    int maxDistance = 0;
    for (TileMap::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it) {
        IntRect rect = it->value->enclosingScaledRect();
        int distance = rect.distanceSquaredToPoint(center);
        if (distance > maxDistance) {
            maxDistance = distance;
            candidate = it;
        }
    }

    if (candidate != m_tiles.end()) {
        m_tiles.remove(candidate);
        return true;
    }

    return false;
}

void TiledBufferPainter::paint(tilebackend::PlatformSurface* target, const IntRect& viewRect)
{
    if (!target)
        return;
    if (!m_committedScale)
        return;

    tilebackend::PainterInterface* painter = m_painter;

    IntSize tileSize = this->tileSize();

    tilebackend::TransformationMatrix matrix = { };
    matrix.m[0][0] = matrix.m[1][1] = matrix.m[2][2] = matrix.m[3][3] = 1;

    // acquire lock to copy tile map
    m_mutex.lock();
    TileMap tiles;
    float committedScale = m_committedScale;
    for (TileMap::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
        tiles.set(it->key, it->value);
    m_mutex.unlock();

    if (!painter)
        painter = &s_fallbackPainter;

    tilebackend::Color color;
    color.rgba[0] = m_backgroundColor.red()   / 255.0f;
    color.rgba[1] = m_backgroundColor.green() / 255.0f;
    color.rgba[2] = m_backgroundColor.blue()  / 255.0f;
    color.rgba[3] = m_backgroundColor.alpha() / 255.0f;

    // begin paint
    painter->beginPaint(target);

    // scale
//    float contentsScale = m_contentsScale;
    float contentsScale = (float)target->height / viewRect.height();
    float scale = 1.0f;

    // translate
    matrix.m[0][3] = -viewRect.x() * contentsScale;
    matrix.m[1][3] = -viewRect.y() * contentsScale;

    if (contentsScale != committedScale)
        scale = contentsScale / committedScale;
    if (scale != 1.0f) {
        matrix.m[0][0] *= scale;
        matrix.m[1][1] *= scale;
    }

    // calculate required tile coord
    FloatRect floatViewRect(viewRect);
    floatViewRect.scale(committedScale);
    floatViewRect.scale(1.0f / tileSize.width(), 1.0f / tileSize.height());
    IntRect coverRect = enclosingIntRect(floatViewRect);
    
    for (int y = coverRect.y(); y < coverRect.maxY(); y++) {
        for (int x = coverRect.x(); x < coverRect.maxX(); x++) {
            TiledBuffer::Coordinate coord(x, y);
            // check intersection
            IntRect tileRect = TiledBuffer::enclosingScaledRect(tileSize, coord, committedScale);
            if (!tileRect.intersects(viewRect)) {
                // CRASH();
                continue;
            }

            // do paint

            // get TiledBuffer
            RefPtr<TiledBuffer> tilePtr = tiles.get(coord);

            if (tilePtr && tilePtr->isValid()) {
                tileRect = tilePtr->tileRect();
                tilebackend::FloatRect rect;
                rect.x = tileRect.x();
                rect.y = tileRect.y();
                rect.w = tileRect.width();
                rect.h = tileRect.height();

                painter->drawTexture(tilePtr->texture(), rect, matrix, 1);
            } else {
                tileRect = TiledBuffer::tileRect(tileSize, coord);
                tilebackend::FloatRect rect;
                rect.x = tileRect.x();
                rect.y = tileRect.y();
                rect.w = tileRect.width();
                rect.h = tileRect.height();

                painter->drawSolidColor(rect, matrix, color);
            }
        }
    }

    // end paint
    painter->endPaint();
}

void TiledBufferPainter::invalidate(const IntRect& updateRect, bool immediate)
{
    // check if updateRect is contained by other rects
    unsigned size = m_dirtyRects.size();
    for (unsigned i = 0; i < size; ++i) {
        if (m_dirtyRects[i].contains(updateRect))
            return;
    }

    m_dirtyRects.append(updateRect);
}

void TiledBufferPainter::clear()
{
    for (TileMap::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
        it->value->setValid(false);

    WTF::MutexLocker locker(m_mutex);
    m_tiles.clear();
}

} // namespace WebCore

static void showPaintTime(cairo_t *cr, double paintTime)
{
    char buf[64];
    snprintf(buf, 64, "%.1f ms", paintTime);
    cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
    cairo_set_font_size(cr, 15.0);
    cairo_move_to(cr, 0.0, 15.0);
    cairo_show_text(cr, buf);
}

#if 0
static JSValueRef getTileProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
{
    if (JSStringIsEqualToUTF8CString(propertyName, "showDirtyRect"))
        return JSValueMakeBoolean(ctx, gShowDirtyRects);

    if (JSStringIsEqualToUTF8CString(propertyName, "showPaintTime"))
        return JSValueMakeBoolean(ctx, gShowPaintTime);

    return 0;
}

static bool setTileProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
{
    if (JSStringIsEqualToUTF8CString(propertyName, "showDirtyRect") && JSValueIsBoolean(ctx, value)) {
        gShowDirtyRects = JSValueToBoolean(ctx, value);
        return true;
    }

    if (JSStringIsEqualToUTF8CString(propertyName, "showPaintTime") && JSValueIsBoolean(ctx, value)) {
        gShowPaintTime = JSValueToBoolean(ctx, value);
        return true;
    }

    return false;
}
#endif
